package tester;

import java.lang.reflect.*;
import java.util.Set;
import java.util.TreeSet;

import tester.cobertura.AnnotationScanner;
import tester.cobertura.Instrumentor;
import tester.utilities.AnnotatedTest;

/**
 * Copyright 2008 Viera K. Proulx, Matthias Felleisen
 * This program is distributed under the terms of the 
 * GNU Lesser General Public License (LGPL)
 */

/**
 * <P>
 * The <code>main</code> method in this class uses the command-line argument to
 * determine the class that contains the test cases. If no class name is given
 * "Examples" is assumed.
 * </P>
 * <P>
 * The main method instantiates this class and invokes the
 * <CODE>{@link Tester Tester}</CODE> on this new instance.
 * </P>
 * 
 * @author Viera K. Proulx, Matthias Felleisen, Weston jossey
 * @since 3 March 2008, 16 October 2008, 20 December 2008
 * 
 */
public class Main {

	/**
	 * Method to create <code>String[]</code> representation of 
	 * a given <code>TreeSet</code>. The TreeSet should contain
	 * <code>ExampleWrapper</code> objects.
	 * 
	 * @param ts
	 * 			The TreeSet to be iterated over
	 * @return A String[] of qualified path names for classes in packages
	 */
	public static String[] examplePaths(TreeSet<ExampleWrapper> ts){
		String[] paths = new String[ts.size()];
		Object[] wrappers = ts.toArray();
		ExampleWrapper current;
		for(int i = 0; i < ts.size(); i++){
			current = (ExampleWrapper) wrappers[i];
			if(current.getPackageName().equals("")){
				paths[i] = current.getClassName();
			} else{
				if(current.getClassName().equals("")){
					paths[i] = current.getPackageName();
				}else{
					paths[i] = current.getPackageName() + "." + current.getClassName();
				}
				paths[i] = current.getPackageName() + "." + 
				           current.getClassName();
			}
		}

		return paths;
	}

	/**
	 * Method to process command line arguments, creating ExampleWrapper classes accordingly.
	 * 
	 * @param argv
	 * 			String array to be processed
	 * @return A TreeSet of ExampleWrapper objects
	 */
	public static TreeSet<ExampleWrapper> processCommandLineArgs(String argv[]){
		String pack, classn;
		TreeSet<ExampleWrapper> classnames = new TreeSet<ExampleWrapper>();
		int i = 0;
		while(i < argv.length){
			if(argv[i].equals("-p") && i + 1 < argv.length){ //if first arg is -p and there are more args
				pack = argv[i+1]; // pack = arg after -p
				if(i+3 < argv.length && argv[i+2].equals("-n")){//if -n is specified immediately following
					classn = argv[i+3]; // use that classname
					i += 4; // increment arg pointer by 4
				} else{ //else use empty string
					classn = ""; 
					i += 2; // increment arg pointer by 2
				}
				classnames.add(new ExampleWrapper(pack, classn));
			} else{
				pack = "";
				if(argv[i].equals("-n") && i+1 < argv.length){
					classn = argv[i+1];
					i += 2;
				} else{//given arguments not using -n or -p, so throw illegal arg exception
					throw new IllegalArgumentException("Improper format of command line arguments.");
				}
				classnames.add(new ExampleWrapper(pack, classn));
			}
		}

		return classnames;
	}

	/**
	 * Method that creates an instance of the <code>Tester</code> and an
	 * instance of the class that defines the tests, invokes the test evaluation
	 * by the <code>Tester</code>, and reports the results.
	 * 
	 * @param argv
	 *            [optional] the name of the class that defines the tests
	 * @throws Exception 
	 */
	public static void main(String argv[]) throws Exception {
		//pool = Executors.newCachedThreadPool(); //For concurrency

		/* 2.0 Specific Code */
		Instrumentor inst = new Instrumentor();
		Set<String> instrumentedClasses = new AnnotationScanner(tester.cobertura.Instrument.class).scan();
		if(instrumentedClasses != null){
			inst.instrumentClasses();
		}


		String classnames[] = null;
		if ((argv == null) || (Array.getLength(argv) == 0)){
			classnames = new String[1];
			classnames[0] = "Examples";
		} else{
			try{
				TreeSet<ExampleWrapper> wrappers = Main.processCommandLineArgs(argv);
				classnames = Main.examplePaths(wrappers);
			} catch(IllegalArgumentException e){
				System.err.println("Unable to decipher command line arguments: "
						+ "\nImproperly formatted command line arguments.");
			}
		}

		AnnotationScanner scanner = new AnnotationScanner(
				tester.Example.class);
		Set<String> classes = null;

		try {
			classes = scanner.scan();

			// get the name of the class that defines the tests
			for (String clazz : classes){
				new AnnotatedTest(clazz).run();
				System.out.println(clazz);
				//pool.execute(new AnnotatedTest(clazz));
			}

		} catch (Exception e) {
			System.err.println("Unable to scan for the annotated classes.\n" +
			"Shutting down...");
		}finally{

			/* 2.0 Specific Code */
			if(instrumentedClasses != null){
				inst.generateReports();
			}			

		}

		if(classes == null){
			System.out.println("Tester Results");

			// construct an instance of the class that defines the tests
			Object o = null;

			Class<?> examples;
			if(classnames == null){
				System.err.println("No Examples class(es) specified!");
			}
			for(String path : classnames){
				try {
					examples = Class.forName(path);

					try {
						Constructor<?> construct = examples
						.getDeclaredConstructor();
						construct.setAccessible(true);
						o = construct.newInstance();

						System.out.println("Tester Results");
					} catch (NoSuchMethodException ex) {
						System.out.println("no default constructor: "
								+ ex.getMessage());
					} catch (InvocationTargetException ex) {
						System.out.println("Invocation: " + ex.getMessage());
					}
					// run tests if the instance was successfully constructed
					if (o != null) {

						/* 2.0 Specific Code */
						if(instrumentedClasses != null){
							inst.instrumentClasses();
						}

						Tester t = new Tester();
						t.runAnyTests(o);

						/* 2.0 Specific Code */
						if(instrumentedClasses != null){
							inst.generateReports();
						}


					}

					// report the problem if an instance of the class that defines the
					// tests
					// has not been successfully constructed
					else
						throw new RuntimeException("o is null");
				}

				// report errors:
				// wrong classname:
				catch (InstantiationException c) {
					System.err.println("Cannot construct an instance of "
							+ path);
					//System.out.println("Cannot construct an instance of "
					//		+ classname);
				} catch (IllegalAccessException c) {
					System.err.println(c.getLocalizedMessage());
				} catch (ClassNotFoundException c) {
					System.err.println(path + " class doesn't exist");
					//System.out.println(classname + " class doesn't exist");
				} catch (ClassCastException c) {
					System.err.println("Examples is expected to extend Main");
					//System.out.println("Examples is expected to extend Main");
				}
				// finish up
				finally {
					System.out.println(" ");
				}
			}
		}

		System.exit(0); //Some bug is causing the threads not to terminate
	}
}
